package com.mini.common.redis.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.Date;
import java.util.List;

/**
 * 流水号生成工具
 *
 * @author Zhou Feng
 */
@Component
public class SerialNumberGenerator {
    private static final int TEN = 10;
    private static final long ONE_DAY = 1L;
    private static final String incrLuaScript =
            "local key = KEYS[1]\n" +
                    "local time = tonumber(ARGV[1])\n" +
                    "local current = redis.call('incr', key)\n" +
                    "redis.call('expire', key, time)\n" +
                    "return tonumber(current)";

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 自动生成流水号
     *
     * @param prefix  前缀
     * @param pattern 日期格式
     * @param length  流水号长度
     * @return 生成的流水号
     */
    public String generate(String prefix, String pattern, int length) {
        String key = "GENERATOR:" + prefix;
        Long serialNumber = redisTemplate.opsForValue().increment(key);
        if (serialNumber.intValue() >= (int) Math.pow(TEN, length)) {
            throw new DuplicateKeyException("今日流水号已达最大值！");
        }
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime nextDay = now.plusDays(ONE_DAY);
        Instant instant = LocalDateTime.of(nextDay.getYear(), nextDay.getMonth(), nextDay.getDayOfMonth(), 0, 0, 0)
                .atZone(ZoneId.systemDefault()).toInstant();
        //每天 00:00 过期
        redisTemplate.expireAt(key, Date.from(instant));
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
        String formatTime = now.format(formatter);
        String formatSerialNumber = String.format("%0" + length + "d", serialNumber);
        return prefix + formatTime + formatSerialNumber;
    }

    /**
     * 自动生成流水号
     *
     * @param key    redis key
     * @param numberPrefix 序列号前缀
     * @param pattern      日期格式
     * @param length       流水号位数
     * @return 生成的流水号: 序列号前缀+格式化日期+自增流水号
     */
    public String generate(String key, String numberPrefix, String pattern, int length) {
        //读取 lua 脚本
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(incrLuaScript);
        //必须设置
        redisScript.setResultType(Long.class);
        // redis中的key
        List<String> keys = Collections.singletonList(key);

        LocalDateTime now = LocalDateTime.now();
        LocalDateTime nextDay = now.plusDays(ONE_DAY);
        LocalDateTime expireDate = LocalDateTime.of(nextDay.getYear(), nextDay.getMonth(), nextDay.getDayOfMonth(), 0, 0, 0);
        // 距离过期时间的秒数
        long seconds = Duration.between(now, expireDate).getSeconds();
        //执行 lua脚本，产生新的序列号
        Long serialNumber = redisTemplate.execute(redisScript, keys, seconds);
        if (serialNumber.intValue() >= (int) Math.pow(TEN, length)) {
            throw new DuplicateKeyException("今日流水号已达最大值！");
        }
        String formatTime = now.format(DateTimeFormatter.ofPattern(pattern));
        String formatSerialNumber = String.format("%0" + length + "d", serialNumber);
        return numberPrefix + formatTime + formatSerialNumber;
    }
}
